/* Gluten Library -- Texture
 *
 * A "classy" interface to an OpenGL texture resource.
 *
 * $AUTHOR$    res
 * $UPDATE$    r38
 */

/* LICENSES ********************************************************************

Copyright (c) 2013- Reuben E. Smith

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*******************************************************************************/

#ifndef GLUTEN_TEXTURE_HPP
#define GLUTEN_TEXTURE_HPP

// Uncomment if your driver fails to support either glGenerateMipmap* or
// GL_GENERATE_MIPMAP. This will fallback to gluBuild*Mipmaps and has all the
// requirements of that method.
#define GLUTEN_TEXTURE_MIPMAP_FALLBACK

#ifdef DEBUG
   #include <cassert>
#endif

#include <GL/glew.h>
#include "Reports.hpp"


namespace Gluten
{
   //! \struct Image
   //! \brief Describes an image resource.
   struct Image
   {
      float*         data;       // normalized [0, 1] values
      unsigned int   layers;
      unsigned int   channels;
      unsigned int   width;
      unsigned int   height;


      //! \brief Default constructor.
      Image() : 
         data(nullptr), 
         layers(0),
         channels(0),
         width(0), 
         height(0) 
      {
      }
   };


   //! \class ImageLoader
   //! \brief Base-class functor for loading images from files.
   class ImageLoader
   {
   public:
      virtual ~ImageLoader() {}

      //! \brief Load image from given file.
      //! \param[in]    path           file to load
      //! \return       image file with loaded data
      //!
      //! In the event of failure, image.data will be null.
      virtual Image operator()(const std::string& path) = 0;
   };


   //! \class BitmapImageLoader
   //! \brief Functor for loading bitmap images from files.
   class BitmapImageLoader
   {
   public:
      //! \brief Load bitmap image from given file.
      //! \param[in]    path           file to load
      //! \return       image file with loaded data
      //!
      //! This supports loading packed bitmaps in bottom-up or top-down order
      //! with 1-, 4-, 8-, 24-, and 32-bits per color. 32-bit color bitmaps
      //! will attempt to read alpha channel data. No compression methods are
      //! currently implemented.
      virtual Image operator()(const std::string& path);
   };


   enum TextureWrap
   {
      TW_CLAMP_BORDER,
      TW_CLAMP_EDGE,
      TW_REPEAT,
      TW_REPEAT_MIRROR,
   };


   enum TextureFilter
   {
      TF_POINT,
      TF_BILINEAR,
   };


   enum MipmapFilter
   {
      MF_NONE,
      MF_POINT,
      MF_TRILINEAR,
   };


   class Texture
   {
   public:
      static Texture* prevTexture;
      static Texture* currTexture;

      ~Texture();

      operator bool() const;

      void upload();
      void commit();

      void bind();
      void unbind();

      void setImage(Image& image);

      void setFilters(TextureFilter min, TextureFilter mag, MipmapFilter mip);
      void setWraps(TextureWrap s, TextureWrap t, TextureWrap r = TW_REPEAT);


   private:
      friend class TextureFactory;

      GLenum         _textureTarget;
      GLuint         _textureID;
      Image          _image;

      MipmapFilter   _filterMip;
      TextureFilter  _filterMin;
      TextureFilter  _filterMag;
      TextureWrap    _wrap[3];


      Texture();
   };


   class TextureFactory
   {
   public:
      TextureFactory();

      void setDefaultFilters(
         TextureFilter min, TextureFilter mag, MipmapFilter mip);

      void setDefaultWraps(TextureWrap s, TextureWrap t, TextureWrap r);


      template < typename Loader >
      Texture* create(const std::string& path)
      {
         Loader imageLoader;
         Image imageResource = imageLoader(path);

         if (imageResource.data == nullptr) {
            return nullptr;
         }

         while (glGetError() != GL_NO_ERROR);

         GLuint textureID = 0;
         glGenTextures(1, &textureID);

         bool hasError = false;
         GLenum error = GL_NO_ERROR;

         while ((error = glGetError()) != GL_NO_ERROR) {
            hasError = true;
            g_reports("error", "TextureFactory::operator()", 
               "failed to generate texture (error " + 
               std::to_string(static_cast<long long>(error)) + ")");
         }

         if (textureID == 0) {
            hasError = true;
            g_reports("error,info", "TextureFactory::operator()",
               "failed to generate texture (id was 0)");
         }

         if (hasError) {
            return nullptr;
         }

         Texture* r = new Texture();
         r->_textureTarget = GL_TEXTURE_2D;
         r->_textureID = textureID;
         r->_image = imageResource;
         r->_filterMip = _filterMip;
         r->_filterMin = _filterMin;
         r->_filterMag = _filterMag;
         r->_wrap[0] = _wrap[0];
         r->_wrap[1] = _wrap[1];
         r->_wrap[2] = _wrap[2];

         return r;
      }


   private:
      MipmapFilter   _filterMip;
      TextureFilter  _filterMin;
      TextureFilter  _filterMag;
      TextureWrap    _wrap[3];
   };
}

#endif

